//#include <math.h>
//#include <stdio.h>
//#include <strings.h>
//#include <sys/stat.h>
//#include <sys/types.h>
//#include <time.h>

#include "emulator_interface.h"
#include "thread.h"
#include "processor.h"
#include "uarch/mreq.h"
#include "uarch/settings.h"
#include "uarch/sim.h"
#include "uarch/types.h"

#if (SESC_EMUL)
#include "ThreadContext.h"
#include "Instruction.h"
#endif

extern Simulator Sim;

Emulator_interface::Emulator_interface ()
{
    /** Handled in init_emul.  */
}

Emulator_interface::~Emulator_interface ()
{
    for (int i = 0; i < Sim.settings.threads_per_processor; i++)
    { 
       processor_map[i].clear ();
    }

    while (!thread_ready_list.empty ())
    {
        Thread *t = thread_ready_list.back ();
        thread_ready_list.pop_back ();
        delete t;
    }

    while (!thread_stalled_list.empty ())
    {
        Thread *t = thread_stalled_list.back ();
        thread_stalled_list.pop_back ();
        delete t;
    }

    while (!thread_exited_list.empty ())
    {
        Thread *t = thread_exited_list.back ();
        thread_exited_list.pop_back ();
        delete t;
    }
}

/** Returns true if there is a processor with a free context.  */
bool Emulator_interface::free_processor_p ()
{
    for (int i = 1; i <= Sim.settings.threads_per_processor; i++)
    {
        if (!processor_map[i].empty ())
            return true;
    }
    return false;
}

/** Return the next processor with the greatest number of free contexts.  */
processor *Emulator_interface::get_next_processor ()
{
    processor *pr;

    pr = NULL;
    for (int i = Sim.settings.threads_per_processor; i > 0; i--)
    {
        if (processor_map[i].empty ())
            continue;

        pr = processor_map[i].front ();

        if (pr->free_contexts () != i)
        {
            //fprintf (stderr, "active: %d i: %d\n", pr->active_contexts (), i);
            fatal_error ("emulator_interface.cpp::get_next_processor () - Active != priority\n");
        }
        break;
    }
    //fprintf (stderr, "emulator_interface.cpp::get_next_processor returning Pr[%d]\n", pr->moduleID.nodeID);
    return pr;
}

/** Thread should have already been added/removed from processor
    contexts prior to calling update_processor_map!  */
void Emulator_interface::update_processor_map (processor *pr)
{
    bool found;
    LIST<processor*>::iterator it;

    //fprintf (stderr, "emulator_interface.cpp:update_processor_map (%d):\n", pr->moduleID.nodeID);
    //fprintf (stderr, " pr->free_contexts (): %d\n", pr->free_contexts ());

    found = false;
    for (int i = 0; i <= Sim.settings.threads_per_processor; i++)
    {
        for (it = processor_map[i].begin (); it != processor_map[i].end (); it++)
        {
            if ((*it)->moduleID.nodeID == pr->moduleID.nodeID)
            {
                //fprintf (stderr, " found pr in processor_map[%d]\n", i);
                processor_map[i].erase (it);
                //fprintf (stderr, " inserting pr in processor_map[%d]\n", pr->free_contexts ());
                processor_map[pr->free_contexts ()].push_back (pr);
                found = true;
                break;
            }
        }
        if (found)
            break;
    }
    if (!found)
        fatal_error ("emulator_interface.cpp::update_processor_map - didn't find processor!");

}

#if (SESC_EMUL)
/** Init the emulator.  */
void Emulator_interface::init_emul ()
{
    Thread *t;
    processor *pr;

    /** Add all the processors to the idle list.  */
    for (int i = 0; i < Sim.settings.num_nodes; i++)
    {
        if (Sim.get_PR (i)->free_contexts () != Sim.settings.threads_per_processor)
        {
            fatal_error ("Why ohh... Why..\n");
        }
        processor_map[Sim.settings.threads_per_processor].push_back (Sim.get_PR (i));
    }
    
    /** Add all the threads to the ready and stalled list.  */
    for (unsigned int i = 0; i < ThreadContext::pid2context.size (); i++)
    {
        if (ThreadContext::pid2context[i] &&
            ThreadContext::pid2context[i]->isExited ())
            continue;

        t = new Thread (ThreadContext::pid2context[i]);
        
        if (ThreadContext::pid2context[i]->isSuspended ())
        {
            t->state = THREAD_STALLED;
            thread_stalled_list.push_back (t);
        }
        else 
        {
            t->state = THREAD_READY;
            thread_ready_list.push_back (t);
        }        
    }

    /** Schedule the ready threads.  */
    while (!thread_ready_list.empty () && free_processor_p ())
    {
        pr = get_next_processor ();
        t = thread_ready_list.front ();
        thread_ready_list.pop_front ();

        t->cpu = pr->moduleID.nodeID;
        t->state = THREAD_RUNNING;
        pr->assign_thread (t);
        update_processor_map (pr);
    }

#if 0
    MAP<int, Thread*>::iterator it;
    for (it = thread_map.begin (); it != thread_map.end (); it++)
    {
        (*it).second->dump();
    }
    exit (0);
#endif
}
#endif

#if (!SESC_EMUL)
/** Init the synthetic trace generator.  */
void Emulator_interface::init_trace ()
{
    Thread *t;

    cerr << "Caffeine Sim - Initializing trace simuation.." << endl;
    Sim.settings.sesc_rabbit = false;

    for (int i = 0; i < Sim.settings.num_nodes; i++)
    {
        for (int j = 0; j < Sim.settings.threads_per_processor; j++)
        {
            t = new Thread (i*Sim.settings.threads_per_processor + j);
            t->state = THREAD_RUNNING;
            t->sched_time = Global_Clock;
            t->cpu = i;
            Sim.get_PR(i)->assign_thread (t);
        }
    }

    /** Add all the processors to the idle list.  */
    for (int i = 0; i < Sim.settings.num_nodes; i++)
    {
        if (Sim.get_PR (i)->free_contexts () != 0)
        {
            fatal_error ("Why ohh... Why..\n");
        }
        processor_map[0].push_back (Sim.get_PR (i));
    }

}
#endif

/** This context was just suspended.  Find it and move it to the suspended list.  */
void Emulator_interface::thread_suspend (int tid)
{
    Thread *t;

    /** Nothing to do while in rabbit mode.  */
    if (Sim.settings.sesc_rabbit)
        return;

    if (GENERAL_DEBUG)
        fprintf (stderr, "Caffeine Sim - thread suspend: %d\n", tid);

    t = thread_map[tid];
    assert (t->state == THREAD_RUNNING);
    
    /** Make the thread stalled and add to stalled list.  */
    thread_stalled_list.push_front (t);
    t->state = THREAD_STALLED;
    return;
}

/** This context was just resumed.  Find it and move it to the ready list.  */
void Emulator_interface::thread_resume (int tid)
{
    Thread *t;
    processor *pr;
    LIST<Thread*>::iterator it;

    /** Nothing to do while in rabbit mode.  */
    if (Sim.settings.sesc_rabbit)
        return;

    if (GENERAL_DEBUG)
        fprintf (stderr, "Caffeine Sim - thread resume: %d\n", tid);

    t = thread_map[tid];

    assert (t->state == THREAD_STALLED);

    /** Remove from stalled, add to ready.  */
    thread_stalled_list.remove (t);
    thread_ready_list.push_back (t);
    t->state = THREAD_READY;    

    /** Schedule the ready threads.  */
    while (!thread_ready_list.empty () && free_processor_p ())
    {
        /** Grab next ready thread.  */
        t = thread_ready_list.front ();
        thread_ready_list.pop_front ();

        /** Processor affinity enabled and previous processor has idle context.  */
        if (Sim.settings.processor_affinity == true && 
            t->cpu != -1 && Sim.get_PR (t->cpu)->free_contexts () > 0)
        {
            pr = Sim.get_PR (t->cpu);
        }
        else
        {
            pr = get_next_processor ();
        }

        t->cpu = pr->moduleID.nodeID;
        t->state = THREAD_RUNNING;
        t->sched_time = Global_Clock;
        pr->assign_thread (t);
        update_processor_map (pr);
    }
}

/** This context was just exited.  */
void Emulator_interface::thread_exit (int tid)
{
    Thread *t;

    /** Nothing to do while in rabbit mode.  */
    if (Sim.settings.sesc_rabbit)
        return;

    if (GENERAL_DEBUG)
        fprintf (stderr, "Caffeine Sim - thread exit: %d\n", tid);

    t = thread_map[tid];
    assert (t->state == THREAD_RUNNING);

    /* If the thread has exited, then add it to the exited list.  */
    thread_exited_list.push_front (t);
    t->state = THREAD_EXITED;
    return;
}

/** Called whenever a context becomes free.  */
void Emulator_interface::schedule ()
{
    Thread *t;
    processor *pr;

    /** Try to schedule on newly idle processor.  */
    if (!thread_ready_list.empty () && free_processor_p ())
    {
        t = thread_ready_list.front ();
        thread_ready_list.pop_front ();

        pr = get_next_processor ();

        t->cpu = pr->moduleID.nodeID;
        t->state = THREAD_RUNNING;
        t->sched_time = Global_Clock;
        pr->assign_thread (t);
        update_processor_map (pr);
    }
}

#if (SESC_EMUL)
/** Emulator just spawned a new ThreadContext.  Schedule it on a processor if possible, otherwise
    put it on the idle thread list.  */
void Emulator_interface::sysClone (ThreadContext *context)
{
    Thread *t;
    processor *pr;

    /** Nothing to do while in rabbit mode.  */
    if (Sim.settings.sesc_rabbit)
        return;

    t = new Thread (context);

    assert (!context->isExited ());

    /** Put stalled contexts on stalled list.  */
    if (context->isSuspended ()) 
    {
        thread_stalled_list.push_front (t);
        t->state = THREAD_STALLED;
        return;
    }

    /** Try to schedule the thread on an idle processor.  */
    if (free_processor_p ())
    {
        pr = get_next_processor ();
        t->cpu = pr->moduleID.nodeID;
        t->state = THREAD_RUNNING;
        pr->assign_thread (t);
        update_processor_map (pr);
    }
    /** No idle processors, add to ready list.  */
    else {
        thread_ready_list.push_back (t);
        t->state = THREAD_READY;
    }
}
#endif

